package main

import (
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"time"

	"../logger"
	"../util"
	"github.com/boltdb/bolt"
)

type Store interface {
	Get(map[string][]Condition) ([]map[string]string, error)
	BatchPut([]map[string]string) error
	Close() error
}

func NewStore(basepath, dbname string) (s Store, err error) {
	s, err = NewBoltStore(basepath, dbname)
	return
}

type BoltStore struct {
	fn string
	db *bolt.DB
}

func NewBoltStore(basepath, dbname string) (Store, error) {
	fn := path.Join(basepath, dbname)
	db, err := bolt.Open(fn, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltStore{
		fn: fn, db: db,
	}, nil
}

func GetBoltStore(basepath, dbname string) (Store, error) {
	fn := path.Join(basepath, dbname)
	_, err := os.Stat(fn)
	if os.IsNotExist(err) {
		return nil, util.FileNotExit
	}
	db, err := bolt.Open(fn, 0600, nil)
	if err != nil {
		return nil, err
	}
	return &BoltStore{
		fn: fn, db: db,
	}, nil
}

func PrepareStore(basepath string, day int) map[string]map[string]Store {
	tables := make(map[string]map[string]Store)
	now := time.Now()
	nDayAgo := now.AddDate(0, 0, (-1)*day)
	dates := getDates(nDayAgo, now)
	// path: ./date/dbname
	for _, date := range dates {
		dbpath := path.Join(basepath, "data", date)
		dbDir, err := ioutil.ReadDir(dbpath)
		if err != nil {
			continue
		}
		tables[date] = make(map[string]Store)
		for _, dbfile := range dbDir {
			dbname := dbfile.Name()
			db, err := NewStore(dbpath, dbname)
			if err != nil {
				logger.Errorln("Error occurs while opening db: " + date + "/" + dbname)
				return nil
			}
			tables[date][dbname] = db
		}
	}
	return tables
}

func (b *BoltStore) BatchPut(data []map[string]string) error {
	return b.db.Batch(func(tx *bolt.Tx) error {
		for _, record := range data {
			buck, err := tx.CreateBucketIfNotExists([]byte(record["DATETIME"]))
			if err != nil {
				logger.Errorln("Create bucket error: %v", err)
				return err
			}
			buck, err = buck.CreateBucketIfNotExists([]byte(record["IP"]))
			if err != nil {
				logger.Errorln("Create bucket error: %v", err)
				return err
			}
			buck, err = buck.CreateBucketIfNotExists([]byte(record["STAFF_ID"]))
			if err != nil {
				logger.Errorln("Create bucket error: %v", err)
				return err
			}
			for k, v := range record {
				err = buck.Put([]byte(k), []byte(v))
				if err != nil {
					logger.Errorln("Fail to put data to boltdb: %v", err)
					return err
				}
			}

		}
		return nil
	})
}

func (b *BoltStore) Close() error {
	return b.db.Close()
}

func judge(key string, val string, cnds map[string][]Condition) (bool, error) {
	var flag bool
	var cmperr error
	if cnds[key] != nil {
		for _, cond := range cnds[key] {
			if cond.op == ">" {
				flag, cmperr = GreaterThan(val, cond.val)
			} else if cond.op == "=" {
				if key == "DATETIME" {
					var condval, boltval string
					if left, err1 := Time2Unix(val); err1 == nil {
						logger.Errorln("Parse time error: " + val)
						return false, util.ParseArgsErr
					} else {
						boltval = strconv.FormatInt(left, 10)
					}
					if right, err2 := Time2Unix(cond.val); err2 == nil {
						logger.Errorln("Parse time error: " + cond.val)
						return false, util.ParseArgsErr
					} else {
						condval = strconv.FormatInt(right, 10)
					}
					flag, cmperr = Equal(boltval, condval)
				} else {
					flag = (val == cond.val)
				}
			} else if cond.op == "<" {
				flag, cmperr = LessThan(val, cond.val)
			} else {
				logger.Errorf("Parse query condition error: %v", cond)
				return false, util.ParseArgsErr
			}
			if cmperr != nil {
				return false, util.ParseArgsErr
			}
			if flag == false {
				return false, nil
			}
		}
	}
	return true, nil
}

func (b *BoltStore) Get(cnds map[string][]Condition) ([]map[string]string, error) {
	var err error
	var result []map[string]string
	var satisfied bool
	err = b.db.View(func(tx *bolt.Tx) error {
		// DATETIME->IP->STAFF_ID -> KV
		return tx.ForEach(func(datetime []byte, date_bkt *bolt.Bucket) error {
			// judge DATETIME
			satisfied, err = judge("DATETIME", string(datetime), cnds)
			if err != nil {
				return err
			} else if !satisfied {
				return nil
			}

			dateCur := date_bkt.Cursor()
			for ip, _ := dateCur.First(); ip != nil; ip, _ = dateCur.Next() {
				// judge IP
				satisfied, err = judge("IP", string(ip), cnds)
				if err != nil {
					return err
				} else if !satisfied {
					return nil
				}

				ip_bkt := date_bkt.Bucket(ip)
				ipCur := ip_bkt.Cursor()

				for staff, _ := ipCur.First(); staff != nil; staff, _ = ipCur.Next() {
					// judge staffNo
					satisfied, err = judge("STAFF_ID", string(staff), cnds)
					if err != nil {
						return err
					} else if !satisfied {
						return nil
					}

					staff_bkt := ip_bkt.Bucket(staff)
					staffCur := staff_bkt.Cursor()
					match := true
					element := make(map[string]string)

					// judge KV
					for k, _ := range cnds {
						if k == "DATETIME" || k == "IP" || k == "STAFF_ID" {
							continue
						}
						boltval := date_bkt.Get([]byte(k))
						if boltval == nil {
							logger.Printf("[Error] Key %v not found\n", k)
							return util.KeyNotFound
						}
						satisfied, err = judge(k, string(boltval), cnds)
						if err != nil {
							return err
						}
						match = match && satisfied
					}

					if !match {
						return nil
					}

					for key, val := staffCur.First(); key != nil; key, val = staffCur.Next() {
						element[string(key)] = string(val)
					}
					result = append(result, element)
				}

			}
			return nil
		})
	})

	if err != nil {
		return nil, err
	}
	return result, err
}

func LessThan(a, b string) (bool, error) {
	var m, n int
	var err1, err2 error
	if m, err1 = strconv.Atoi(a); err1 != nil {
		logger.Errorln("Fail to parse string to int: " + a)
		return false, err1
	}
	if n, err2 = strconv.Atoi(b); err2 != nil {
		logger.Errorln("Fail to parse string to int: " + b)
		return false, err2
	}
	return m < n, nil
}

func Equal(a, b string) (bool, error) {
	var m, n int
	var err1, err2 error
	if m, err1 = strconv.Atoi(a); err1 != nil {
		logger.Errorln("Fail to parse string to int: " + a)
		return false, err1
	}
	if n, err2 = strconv.Atoi(b); err2 != nil {
		logger.Errorln("Fail to parse string to int: " + b)
		return false, err2
	}
	return m == n, nil
}

func GreaterThan(a, b string) (bool, error) {
	var m, n int
	var err1, err2 error
	if m, err1 = strconv.Atoi(a); err1 != nil {
		logger.Errorln("Fail to parse string to int: " + a)
		return false, err1
	}
	if n, err2 = strconv.Atoi(b); err2 != nil {
		logger.Errorln("Fail to parse string to int: " + b)
		return false, err2
	}
	return m > n, nil
}
